Skip to main content
Rooms are the core abstraction for collaborative sessions in Duet. Each room represents an isolated workspace with a shared terminal and multiple connected clients.

Room Manager

The Manager type in /internal/room/manager.go coordinates all rooms:

Thread-Safe Operations

The manager uses sync.RWMutex for concurrent access:
  • RLock/RUnlock: Used for read operations (GetRoom, RoomCount, GetAIClient)
  • Lock/Unlock: Used for write operations (CreateRoom, LeaveRoom)
This allows multiple clients to read room state simultaneously while preventing race conditions during modifications.

Room Structure

Each room contains:

Key Fields

  • ID: UUID generated at creation time
  • Description: Optional user-provided description (used for workspace naming)
  • Host: Username of the room creator
  • Connections: List of connected clients
  • Terminal: Shared terminal instance (nil until first client starts it)
  • AIMessages: Conversation history for AI chat feature
  • WorkspaceDir: Isolated filesystem directory for this room

Client Structure

Each client has:
  • Unique ID: UUID to distinguish multiple connections from same user
  • Display name: From SSH session username
  • Host flag: Only the creator has this set to true
  • Event channel: Receives room events (join/leave/typing/ai_sync)

Room Lifecycle

1. Room Creation

Workspace Naming:
  • If description provided: slugify("My Project")my-project
  • Otherwise: random name like cosmic-phoenix or brave-dragon
Workspace Template: The server attempts to copy /app/workspace-template/ contents into each workspace, providing pre-configured files or tools. Falls back to an empty directory if template is unavailable.

2. Joining a Room

Clients join by providing the room ID. The manager returns the room reference if it exists.

3. Client Registration

In the UI model (/internal/ui/model.go):
The room broadcasts a join event to all existing clients:
Non-Blocking Sends: The select with default case ensures event sends never block. If a client’s event channel is full, the event is dropped for that client.

4. Leaving a Room

Cleanup Actions:
  1. Remove client from room’s connection list
  2. If last client:
    • Close terminal and kill shell process
    • Delete workspace directory from filesystem
    • Send DELETE request to worker (if configured) to clean up sandbox and AI state
    • Remove room from manager’s map

5. Resource Cleanup

External resources are cleaned up asynchronously:
This runs in a goroutine so it doesn’t block the client’s disconnect.

Event Broadcasting

Room Events

Event Types:
  • join: User joined the room
  • leave: User left the room
  • typing: User is typing in terminal (debounced to 500ms)
  • ai_sync: AI chat messages updated (triggers viewport refresh)

Broadcast Implementation

The excludeClientID parameter prevents echoing events back to the sender.

Workspace Isolation

Each room’s workspace provides:

Directory Structure

Template Files

If /app/workspace-template/ exists, it’s copied to each workspace:

Terminal Working Directory

When starting the terminal:
The shell process executes with cmd.Dir = workDir, providing filesystem isolation.

AI Message Synchronization

Rooms store AI conversation history:

Thread-Safe Access

Sync Flow

  1. Client sends AI message via Ctrl+G
  2. Worker returns updated message history
  3. Client calls currentRoom.SetAIMessages(msgs)
  4. Client broadcasts ai_sync event
  5. Other clients receive event and call GetAIMessages() to refresh their viewport
This ensures all clients see the same AI conversation in real-time.

Concurrency Patterns

Read-Heavy Optimization

sync.RWMutex is used because:
  • Reads (GetRoom, RoomCount) are frequent (every client action)
  • Writes (CreateRoom, LeaveRoom) are infrequent (only on join/leave)
Multiple goroutines can hold read locks simultaneously, improving performance.

Channel-Based Events

Each client has a buffered channel (chan RoomEvent, 10) that:
  • Decouples event producers (broadcast) from consumers (client UI loop)
  • Prevents blocking if client is slow to process
  • Automatically closes when client disconnects

Graceful Reconnection

If a client reconnects with the same ID:
The old channel is closed and the client is re-added with a fresh channel.